package tree;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import lexer.Token;
import lexer.TokenType;

/**
 * Superclass for all Scopes
 */
public class Scope {

	/**
	 * A map from strings to identifiers / methods / classes
	 */
	protected Map<String, Tree> imc = new TreeMap<String, Tree>();

	/**
	 * The list of subscopes of this scope
	 */
	protected List<Scope> children = new LinkedList<Scope>();

	/**
	 * A link to the parent scope.
	 * If there is no parent, this is null.
	 */
	protected Scope parent;

	/**
	 * Creates a new scope.
	 */
	public Scope() {
		this(null);
	}

	/**
	 * Creates a new scope with given parent.
	 * This constructor has to initialize the {@link #parent} field
	 * and add this scope to the list of children of the parent.
	 */
	protected Scope(Scope parent) {
		this.parent = parent;
		if (this.parent != null) {
			parent.children.add(this); 
		}
	}

	/**
	 * Declares a variable in this scope.
	 * @param  decl The tree node of the variable declaration.
	 * 				The token type of the tree's token must be {@link TokenType#IDENT}.
	 * @throws NamingException if this name was already defined.
	 */
	public void declare(Tree decl) throws NamingException {
		String name = decl.getToken().getText();
		if (this.imc.containsKey(name)) {
			throw NamingException.alreadyDefined(decl.getToken());
		}
		this.imc.put(name, decl);
	}

	/**
	 * Lookup an identifier / method / class in the current scope hierarchy.
	 * @param token A token
	 * @throws NamingException
	 */
	public Tree lookup(Token token) throws NamingException {
		if ((parent == null) && (!(this.imc.containsKey(token.getText())))) {
			throw NamingException.undefined(token);
		}
		else if (this.imc.containsKey(token.getText())) {
			return imc.get(token.getText());
		}
		else {
			return this.parent.lookup(token);
		}
	}

	/**
	 * Create a new sub-scope of this scope.
	 * A new sub-scope of this scope is created and returned.
	 * @return The newly created sub-scope.
	 */
	public Scope pushScope() {
		return new Scope(this);
	}

	/** 
	 * String representation of the current Scope
	 * @return String representation
	 */
	public String toString() {
		String result = "(";
		String prefix = "";
		for (String s : imc.keySet()) {
			Tree decl = imc.get(s);
			result += prefix;
			result += decl.getToken().getText();
			prefix = ", ";
		}
		if (prefix.length() > 0) {
			prefix = " ";
		}
		for (Scope s : children) {
			result += prefix;
			result += s.toString();
		}
		result += ")";
		return result;
	}
}
